package cn.codepandas.unifiedresponse.config;

import cn.codepandas.unifiedresponse.annotation.UnifiedResponse;
import cn.codepandas.unifiedresponse.constant.Constants;
import cn.codepandas.unifiedresponse.model.*;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.validation.BindException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ValidationException;

/**
 * 处理自定义的响应结果
 *
 * @author Amaris
 * @since 1.0.0
 */
@RestControllerAdvice
public class UnifiedResponseBodyAdvice implements ResponseBodyAdvice<Object> {

    /**
     * 根据请求是否包含 UNIFIED-RESPONSE 标记判断是否需要重写返回体
     * @param returnType 返回类型
     * @param converterType 转换器类型
     * @return 是否需要重写返回体
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        ServletRequestAttributes sra = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes());
        if(sra != null){
            HttpServletRequest request = sra.getRequest();
            UnifiedResponse unifiedResponseAnn = (UnifiedResponse) request.getAttribute(Constants.TAG);
            return unifiedResponseAnn != null;
        }else {
            return false;
        }
    }

    /**
     * 重写返回体
     * @param body 处理前的响应体
     * @param returnType 返回类型
     * @param selectedContentType
     * @param selectedConverterType
     * @param serverHttpRequest HTTP 请求
     * @param serverHttpResponse HTTP 响应
     * @return 包装的响应结果
     */
    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
        UnifiedResult unifiedResult;
        if(body instanceof BindException || body instanceof ValidationException || body instanceof ServletRequestBindingException){
            // 处理参数校验异常响应
            Exception paramException = (Exception) body;
            IResultCode resultCode = ResultCode.CLIENT_ERROR;
            serverHttpResponse.setStatusCode(HttpStatus.resolve(resultCode.getStatus()));
            unifiedResult = new UnifiedResult(resultCode, paramException.getMessage());
        } else if(body instanceof ResultException) {
            // 处理自定义结果异常响应
            ResultException resultException = (ResultException) body;
            IResultCode resultCode = resultException.getResultCode();
            serverHttpResponse.setStatusCode(HttpStatus.resolve(resultCode.getStatus()));
            unifiedResult = new UnifiedResult(resultCode, resultException.getData());
        } else if(body instanceof Throwable) {
            // 处理其他异常响应（其他异常，错误或自定义的问题）
            Throwable throwable = (Throwable) body;
            IResultCode resultCode = ResultCode.SERVER_ERROR;
            serverHttpResponse.setStatusCode(HttpStatus.resolve(resultCode.getStatus()));
            unifiedResult = new UnifiedResult(resultCode, throwable.getMessage());
        } else if(body instanceof UnifiedResult) {
            unifiedResult = (UnifiedResult) body;
            // 处理自定义响应结果，避免用户同时使用 @UnifiedResult 而造成的数据嵌套
            serverHttpResponse.setStatusCode(HttpStatus.resolve(unifiedResult.getStatus()));
        } else {
            // 处理正常响应
            serverHttpResponse.setStatusCode(HttpStatus.OK);
            unifiedResult = new UnifiedResult(ResultCode.SUCCESS, body);
        }
        return unifiedResult;
    }

}